#include <cstdlib>
#include <cstdio>
#include <cctype>
#include <cerrno>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stdint.h>
#include <unistd.h>


#define     END_OF_UINT8    0x80

class LetterStat
{
public:
    LetterStat(int, char**);
    bool fail() const {return !mReady && mFp == 0;}
    void stat();
    bool ready() const {return mReady;}
    void show() const;
    void showSorted() const;
    
    enum 
    {
        CASE_SENSITIVE = 0,
        CASE_INSENSITIVE = 1,
    };

    enum
    {
        SHOW_ABSOLUTE = 0,
        SHOW_PERCENTAGE = 1,
    };

    enum SortOrder_t
    {
        SORT_ALPHABET = 0,
        SORT_ASCENDING = 1,
        SORT_DESCENDING = 2,
    };

private:
    bool mCaseInsensitive;
    bool mShowPercentage;
    SortOrder_t mSortOrder;
    FILE* mFp;
    bool mReady;

    uint32_t mFrequency[ END_OF_UINT8 ];
    uint32_t mSize;
};

int main(int argc, char** argv)
{
    LetterStat stat(argc, argv);
    if (stat.fail())
        return 1;

    stat.stat();
    stat.show();
    return 0;
}

static void usage(int argc, char** argv)
{
    printf("%s: Statistic frequency of all printable characters within a file\n",
            argv[0]);
    printf("Usage: %s [options] file...\n", argv[0]);
    printf("Built %s %s\n", __TIME__, __DATE__);
    printf("Options:\n");
    printf("  -a    Show in ascending order\n");
    printf("  -d    Show in descending order\n");
    printf("  -i    Counter letters case insensitively\n");
    printf("  -p    Show percentage instead of absolute value\n");
    printf("  -h    Show this help message and exit\n");
}

#define     ANSI_RED    "\e[31m"
#define     ANSI_YELLOW "\e[33m"
#define     ANSI_BOLD   "\e[1m"
#define     ANSI_RESET  "\e[0m"
#define     ANSI_RED_BOLD   ANSI_RED ANSI_BOLD
#define     ANSI_YELLOW_BOLD   ANSI_YELLOW ANSI_BOLD
#define     ERROR       ANSI_RED_BOLD "Error: " ANSI_RESET
#define     WARNING     ANSI_YELLOW_BOLD "Warning: " ANSI_RESET

LetterStat::LetterStat(int argc, char** argv)
    : mCaseInsensitive(CASE_SENSITIVE),
      mShowPercentage(SHOW_ABSOLUTE),
      mSortOrder(SORT_ALPHABET),
      mFp(0),
      mReady(false),
      mSize(0)
{
    int ch;

    while( (ch = getopt(argc, argv, "adiph")) >= 0) {
        switch (ch) {
        case 'a': 
            if (mSortOrder == SORT_DESCENDING) {
                printf(WARNING                    
                       "Sort order overwritten to ascending\n");
            }
            mSortOrder = SORT_ASCENDING;
            break;
        case 'd':
            if (mSortOrder == SORT_ASCENDING) {
                printf(WARNING
                       "Sort order overwritten to decsending\n");
            }
            mSortOrder = SORT_DESCENDING;
            break;
        case 'i':
            mCaseInsensitive = CASE_INSENSITIVE;
            break;
        case 'p':
            mShowPercentage = SHOW_PERCENTAGE;
            break;
        case 'h':
            usage(argc, argv);
            exit(0);
        default:
            break;
        }
    }

    const char* filename = argv[optind];
    if (!filename || filename[0] == 0) {
        fprintf(stderr,
                ERROR
                "no input file\n");
        return;
    }

    mFp = fopen(filename, "rb");
    if (!mFp) {
        fprintf(stderr,
                ERROR
                "cannot open file %s: %s\n",
                filename, strerror(errno));
        return;
    }

    memset(mFrequency, 0, END_OF_UINT8 * sizeof(mFrequency[0]) );
}

inline bool isVisible(char ch)
{
    return (ch == '\t' || ch == '\n' || isprint(ch));
}

#define     BLOCK_SIZE      4096
void LetterStat::stat()
{
    uint8_t block [ BLOCK_SIZE ] = {0};
    int n;
    static const int DIFF = 'a' - 'A';

    while ( (n = fread(block, 1, BLOCK_SIZE, mFp)) > 0) {
        for (int i = 0; i < n; ++i) {
            int ch = block[i];
            if (!isVisible(ch)) {
                printf(WARNING
                        "Non printable character (ASCII 0x%x). Discard\n", ch);
                continue;
            }

            if (mCaseInsensitive == CASE_INSENSITIVE && islower(ch)) {
                ch -= DIFF;
            }

            ++mFrequency[ch];
            ++mSize;
        }
    }

    fclose(mFp);
    mFp = 0;
    mReady = true;
}

inline void printChar(char ch)
{
    printf("%03d       %02x       ", ch, ch);
    switch(ch) {
    case '\t':
        printf("\\t (horizontal tab)  ");
        break;
    case '\n':
        printf("\\n (new line)        ");
        break;
    case ' ':
        printf(" (SPACE)             ");
        break;
    default:
        printf("%c                    ", ch);
        break;
    }
}

#define     FIRST_CHAR_VISIBLE      9
void LetterStat::show() const
{
    if (!mReady) {
        fprintf(stderr, ERROR "Stat not ready\n");
        return;
    }

    
    printf("Dec       Hex      Char                 Frequency\n");
    printf("─────────────────────────────────────────────────────\n");

    if (mSortOrder == SORT_ALPHABET) {
        for (int ch = FIRST_CHAR_VISIBLE; ch < END_OF_UINT8; ++ch) {
            if (!isVisible(ch))
                continue;
            if (mCaseInsensitive == CASE_INSENSITIVE && islower(ch))
                continue;
            printChar(ch);
            
            if (mShowPercentage == SHOW_ABSOLUTE)
                printf("%u\n", mFrequency[ch]);
            else
                printf("%.2lf%%\n", 100.0 * mFrequency[ch] / mSize);
        }
    }
    else{
        showSorted();
    }
}

struct StatEntry
{
    uint8_t key;
    uint32_t value;
};

bool lessThan(const StatEntry& a, const StatEntry& b)
{
    if (a.value < b.value)
        return true;
    if (a.value > b.value)
        return false;
    return a.key < b.key;
}

bool greaterThan(const StatEntry&a, const StatEntry& b)
{
    if (a.value > b.value)
        return true;
    if (a.value < b.value)
        return false;
    return a.key > b.key;
}

void LetterStat::showSorted() const
{
    std::vector<StatEntry> sorter;
    for (int i = FIRST_CHAR_VISIBLE; i < END_OF_UINT8; ++i) {
        if (!isVisible(i))
            continue;
        if (mCaseInsensitive == CASE_INSENSITIVE && islower(i))
            continue;

        StatEntry entry = {(uint8_t)i, mFrequency[i]};
        sorter.push_back(entry);
    }

    if (mSortOrder == SORT_ASCENDING)
        std::sort(sorter.begin(), sorter.end(), lessThan);
    else
        std::sort(sorter.begin(), sorter.end(), greaterThan);

    for (size_t i = 0; i < sorter.size(); ++i) {
        char ch = sorter[i].key;
        uint32_t value = sorter[i].value;
        printChar(ch);

        if (mShowPercentage == SHOW_ABSOLUTE)
            printf("%u\n", value);
        else
            printf("%.2lf%%\n", 100.0 * value / mSize);
    }
}
